home *** CD-ROM | disk | FTP | other *** search
- /* This file is part of the XText package (version 0.8)
- Mike Dixon, April 1992
-
- Copyright (c) 1992 Xerox Corporation. All rights reserved.
-
- Use and copying of this software and preparation of derivative works based
- upon this software are permitted. This software is made available AS IS,
- and Xerox Corporation makes no warranty about the software or its
- performance.
- */
-
- #import "ErrorStream.h"
- #import "XTAction.h"
- #import <sys/types.h>
- #import <nextdev/keycodes.h>
- #import <stdio.h>
- #import <stdlib.h>
- #import <string.h>
-
- /* This file contains all the routines to parse the argument to the
- addBindings:estream: method; it's the most complicated part of the
- whole package. The strategy is simple recursive descent, and we
- make no attempt to recover from errors.
-
- The grammar supported is somewhat more general than necessary; for
- example you can nest sequences of instructions, which currently serves
- no purpose (unless you wanted to get around the maximum sequence
- length...). The idea is just to make it easy to add more complex
- control structure later, if that turns out to be useful.
- */
-
- #define MAX_SEQUENCE_LENGTH 16 // max number of actions in a sequence
- #define MAX_SELECTOR_LENGTH 32 // max length of a selector name
- #define MAX_STRING_LENGTH 256 // max length of a string argument
- #define MAX_ARGS 2 // max number of args to a message
- // (Note that if you increase MAX_ARGS you'll also have to add a new
- // subclass of XTAction and augment parse_msg to use it.)
-
- #define MAX_KEYS 8 // max number of keys affected by a
- // single binding
-
- #define PRE_ERROR_CONTEXT 32 // number of characters displayed before
- #define POST_ERROR_CONTEXT 16 // and after a syntax error
-
- typedef keyCode keySet[MAX_KEYS]; // set of keys an action will be bound to
-
-
- #define ALPHA(c) \
- (((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')) || (c == '_'))
-
- #define WHITE(c) \
- ((c == ' ') || (c == '\n') || (c == '\t') || (c == '\r'))
-
- #define DIGIT(c) \
- ((c >= '0') && (c <= '9'))
-
-
- /* skip_whitespace advances over any white space and returns the first
- non-whitespace character.
-
- Like the rest of the parsing routines, it's passed a pointer to a
- char pointer, which is advanced as the string is consumed.
- */
-
- char skip_whitespace(const char **p)
- {
- while (WHITE(**p))
- ++*p;
- return **p;
- }
-
- /* report_syntax_error is used to report all syntax errors detected during
- parsing. The args are
- error optional information about the type of error
- p a pointer to the place at which the error was detected
- start a pointer to the beginning of the string being parsed
- errs the ErrorStream
-
- The message will contain some or all of the string surrounding the
- error to help identify the problem.
- */
-
- void report_syntax_error(const char *error, const char *p, const char *start,
- ErrorStream *errs)
- {
- char msg[100+PRE_ERROR_CONTEXT+POST_ERROR_CONTEXT];
- const char *prefix;
- int prefix_length;
-
- // display at most PRE_ERROR_CONTEXT characters before the error point...
-
- if (start < (p - PRE_ERROR_CONTEXT)) {
- prefix = p - PRE_ERROR_CONTEXT;
- prefix_length = PRE_ERROR_CONTEXT;
- } else {
- prefix = start;
- prefix_length = p-start;
- }
-
- // ... and at most POST_ERROR_CONTEXT characters after, except that if
- // there weren't many characters before we can put even more after.
-
- sprintf(msg, "Syntax error%s in binding:\n %.*s (here) %.*s",
- error, prefix_length, prefix,
- (PRE_ERROR_CONTEXT+POST_ERROR_CONTEXT-prefix_length), p);
- [errs report:msg];
- }
-
- XTAction *parse_action(const char **p, NXZone *z,
- const char *start, ErrorStream *errs);
-
- /* parse_seq parses a '{}'-delimited, ';'-separated sequence of actions
- and constructs an XTSeqAction out of them. The args are
- p pointer to the current position pointer
- z zone in which to allocate the XTActions
- start the beginning of the string (for reporting errors)
- errs the ErrorStream
-
- If there are no errors, the new XTAction is returned; otherwise the
- result is nil.
- */
-
- XTAction *parse_seq(const char **p, NXZone *z,
- const char *start, ErrorStream *errs)
- {
- // we accumulate the actions in an array on the stack, and then copy
- // them into the specified zone when we find out how many there were.
-
- XTAction *actions[MAX_SEQUENCE_LENGTH];
- int num_actions = 0;
- XTAction **copied_actions = 0;
- char c;
-
- // skip over the open brace
- ++*p;
- while (1) {
- c = skip_whitespace(p);
- if (c == '}') {
- ++*p;
- if (num_actions == 1)
- return actions[0];
- else if (num_actions > 0) {
- size_t size = num_actions * sizeof(XTAction *);
- copied_actions = NXZoneMalloc(z, size);
- memcpy(copied_actions, actions, size);
- }
- return [[XTSeqAction allocFromZone:z]
- initLength:num_actions actions:copied_actions];
- }
- else if (c == ';')
- ++*p;
- else {
- if (num_actions >= MAX_SEQUENCE_LENGTH) {
- report_syntax_error(" (sequence too long)", *p, start, errs);
- return nil;
- }
- if (!(actions[num_actions++] = parse_action(p, z, start, errs)))
- return nil;
- }
- }
- }
-
- /* parse_arg parses a message argument, which must be either an integer
- or a '"'-delimited string. The args are the same as parse_seq, with
- one addition:
- result a pointer to where the result should be stored
-
- Only a few escape sequences are recognized: \n, \t, \\, and \". It
- would be easy to add more.
-
- If there are no errors, the result (coerced to an int) will be stored
- in *result and parse_arg will return true; otherwise it returns false.
- */
-
- BOOL parse_arg(int *result, const char **p, NXZone *z,
- const char *start, ErrorStream *errs)
- {
- char arg[MAX_STRING_LENGTH];
- int arg_length = 0;
- char c;
- char *copied_arg;
-
- c = skip_whitespace(p);
- if (DIGIT(c) || (c == '-') || (c == '+'))
- *result = strtol(*p, p, 0); // ought to check for overflow...
- else if (c == '"') {
- while (1) {
- c = *++*p;
- switch (c) {
- case 0:
- report_syntax_error(" (unterminated string)", *p, start, errs);
- return NO;
- case '"':
- ++*p;
- goto at_end;
- case '\\':
- c = *++*p;
- switch (c) {
- case 'n': c = '\n'; break;
- case 't': c = '\t'; break;
- case '\\':
- case '"': break;
- default:
- report_syntax_error(" (unknown escape sequence)",
- *p, start, errs);
- return NO;
- }
- }
- if (arg_length >= MAX_STRING_LENGTH) {
- report_syntax_error(" (string too long)", *p, start, errs);
- return NO;
- }
- arg[arg_length++] = c;
- }
- at_end:
- copied_arg = NXZoneMalloc(z, arg_length+1);
- memcpy(copied_arg, arg, arg_length);
- copied_arg[arg_length] = '\0';
- *result = (int)copied_arg;
- } else {
- report_syntax_error("", *p, start, errs);
- return NO;
- }
- return YES;
- }
-
- /* parse_msg parses a single message action, such as
- replaceSel:"foobar" length:3
- The args and result are the same as for parse_seq.
- */
-
- XTAction *parse_msg(const char **p, NXZone *z,
- const char *start, ErrorStream *errs)
- {
- char sel_name[MAX_SELECTOR_LENGTH];
- int args[MAX_ARGS];
- int sel_length = 0;
- int num_args = 0;
- char c;
- SEL sel;
- char *error;
-
- c = **p;
- while (1) {
- sel_name[sel_length++] = c;
- if (sel_length >= MAX_SELECTOR_LENGTH) {
- error = " (selector too long)";
- goto syntax_error;
- }
- ++*p;
- if (c == ':') {
- if (num_args >= MAX_ARGS) {
- error = " (too many args)";
- goto syntax_error;
- }
- if (!parse_arg(&args[num_args++], p, z, start, errs))
- return nil;
- skip_whitespace(p);
- }
- c = **p;
- if (!(ALPHA(c) || DIGIT(c) || c == ':'))
- break;
- }
- sel_name[sel_length] = '\0';
- sel = sel_getUid(sel_name);
- if (sel == 0) {
- error = " (unknown selector)";
- goto syntax_error;
- }
- return num_args == 0
- ? [[XTMsg0Action allocFromZone:z] initSel:sel]
- : num_args == 1
- ? [[XTMsg1Action allocFromZone:z] initSel:sel arg:args[0]]
- : [[XTMsg2Action allocFromZone:z] initSel:sel arg:args[0] arg:args[1]];
-
- syntax_error:
- report_syntax_error(error, *p, start, errs);
- return nil;
- }
-
- /* parse_action parses an action, which currently must be either a message
- to be sent to the XText object or a sequence of actions. The args are
- the same as parse_seq.
- */
-
- XTAction *parse_action(const char **p, NXZone *z,
- const char *start, ErrorStream *errs)
- {
- char c;
-
- c = skip_whitespace(p);
- if (ALPHA(c))
- return parse_msg(p, z, start, errs);
- if (c == '{')
- return parse_seq(p, z, start, errs);
- report_syntax_error(((c == 0) ? " (unexpected end)" : ""),
- *p, start, errs);
- return nil;
- }
-
- /* parse_keys parses a specification of the keys an action is to be bound
- to. A specification is a ','-separated sequence, terminated by a '=',
- where each element is zero or more modifiers ('c', 's', 'a', or 'm')
- followed by either a hex key code or ' followed by the character generated
- by the key. In the latter case there may be several keys that generate
- the character; each is added to the set. If there are no errors, the
- key codes are stored in keys and true is returned; otherwise false is
- returned. If there are fewer than MAX_KEYS keys, the first unused entry
- in keys is set to 0 (which happens to be an invalid key code).
- */
-
- BOOL parse_keys(keySet keys, const char **p,
- const char *start, ErrorStream *errs)
- {
- int num_keys = 0;
- int key = 0;
- int i;
- char c;
- BOOL found_one;
- char *error;
-
- while (1) {
- c = skip_whitespace(p);
- found_one = NO;
- switch (c) {
- case 'c': key |= 1; break;
- case 's': key |= 2; break;
- case 'a': key |= 4; break;
- case 'm': key |= 8; break;
- case '0': case '1': case '2': case '3': case '4': case '5':
- key += (c - '0') << 8;
- c = *++*p;
- if (DIGIT(c))
- key += (c - '0') << 4;
- else if ((c >= 'a') && (c <= 'f'))
- key += (10 + c - 'a') << 4;
- else if ((c >= 'A') && (c <= 'F'))
- key += (10 + c - 'A') << 4;
- else {
- error = "";
- goto syntax_error;
- }
- if (num_keys >= MAX_KEYS) {
- error = " (too many keys)";
- goto syntax_error;
- }
- keys[num_keys++] = key;
- found_one = YES;
- break;
- case '\'':
- c = *++*p;
- found_one = NO;
- // skip over the first couple of keys, which don't exist and/or
- // don't generate ascii codes
- for (i = 6; i < (2*MAPPED_KEYS); ++i) {
- // if a key generates the same character shifted and unshifted,
- // don't add them both.
- if ((ascii[i] == c) && (((i&1) == 0) || (ascii[i-1] != c))) {
- if (num_keys >= MAX_KEYS) {
- error = " (too many keys)";
- goto syntax_error;
- }
- keys[num_keys++] = ((i&0xfe) << 3) | key | ((i&1) << 1);
- found_one = YES;
- }
- }
- if (!found_one) {
- error = " (no key for this char)";
- goto syntax_error;
- }
- break;
- default:
- error = "";
- goto syntax_error;
- }
- ++*p;
- if (found_one) {
- c = skip_whitespace(p);
- ++*p;
- if (c == ',')
- {} // go back for more
- else if (c == '=') {
- if (num_keys < MAX_KEYS)
- keys[num_keys] = 0;
- return YES;
- } else {
- error = "";
- goto syntax_error;
- }
- }
- }
-
- syntax_error:
- report_syntax_error(error, *p, start, errs);
- return NO;
- }
-
- @implementation XTDispatchAction(parsing)
-
- /* Finally, here's the method we've been preparing to implement.
- Note that any XTActions generated will be allocated in the same
- zone as the dispatch action.
- */
-
- - addBindings:(const char *)bindings estream:errs
- {
- keySet keys;
- char c;
- const char *cp = bindings;
- XTAction *a;
- NXZone *z = [self zone];
- int i;
-
- if (!errs) errs = [ErrorStream default];
- while (1) {
- c = skip_whitespace(&cp);
- if (c == 0)
- return self;
- if (c == ';')
- ++cp;
- else {
- if (!parse_keys(keys, &cp, bindings, errs))
- return self;
- if (!(a = parse_action(&cp, z, bindings, errs)))
- return self;
- for (i = 0; i < MAX_KEYS; ++i) {
- if (keys[i])
- [self bindKey:keys[i] toAction:a estream:errs];
- else
- break;
- }
- }
- }
- }
-
- @end
-